using System;
using System.Collections;
using UnityEngine;

public class TableTennisAiPlayerRacket : TableTennisPlayerBase
{
	public struct AIHitBallParams
	{
		public HitBallParams hitBallParams;

		public float usedSpeed;
	}

	private bool isInServe;

	public Transform racket;

	public FloatRange speedRange = new FloatRange
	{
		min = 0f,
		max = 1f
	};

	protected Vector3 startMovePos;

	protected Vector3 endMovePos;

	protected bool moving;

	protected float moveTime;

	protected float moveDuration;

	public float pingTime = 45f;

	public float rotateDuration = 0.3f;

	public float rotateMoveBackDuration = 0.1f;

	public float rotateAngle = 35f;

	public Vector3 racketRotateAxis = Vector3.right;

	protected Vector3 additionalMove;

	protected Action onMoveComplete;

	protected Quaternion additionalRotation = Quaternion.identity;

	protected float racketY;

	protected Vector3 racketPosition;

	protected GravityBallFlight serveFlight = new GravityBallFlight();

	public PhysicsBall ball { get; protected set; }

	protected AiPlayerDefinition ai
	{
		get
		{
			return ScriptableObjectSingleton<AiPlayerDefinitions>.instance.GetDefinition(base.match.ui.aiTestButton.playerDefinitionIndex);
		}
	}

	private float scoreDifference
	{
		get
		{
			return base.match.gameType.GetScoreDifference(base.teamTag);
		}
	}

	public override bool IsInServe()
	{
		return isInServe;
	}

	public override void Init(int teamTag, int tableSide, TableTennisMatchController match, bool canAlwaysHit)
	{
		base.Init(teamTag, tableSide, match, canAlwaysHit);
		racket.rotation = Quaternion.AngleAxis(70f, Vector3.forward);
		racketY = Table.tabletopy + 0.2f;
		racketPosition = racket.position;
	}

	private void Update()
	{
		if (isInServe)
		{
			Vector3 position = racket.position + -base.tableSide * Vector3.forward * ball.flightSequence.radius;
			serveFlight.flightTime += Time.deltaTime;
			serveFlight.flightTime = Mathf.Repeat(serveFlight.flightTime, 2f * serveFlight.apexTime);
			position.y = serveFlight.PositionAt(serveFlight.flightTime).y;
			position.x = Mathf.Clamp(position.x, 0f - Table.halfwidth, Table.halfwidth);
			position.z = (float)base.tableSide * Table.halfLength;
			ball.position = position;
		}
		PhysicsBall physicsBall = base.match.ball;
		float num = physicsBall.position.x - racket.position.x;
		racket.rotation = Quaternion.Lerp(racket.rotation, Quaternion.AngleAxis((float)MathEx.SignZeroPositive(num) * Mathf.Lerp(0f, 1f, 4f * Mathf.Abs(num) / Table.halfwidth) * -70f, Vector3.forward), Time.deltaTime * 20f);
		UpdateMove();
		racket.position = racketPosition + additionalMove;
	}

	private void RotateToHit()
	{
		StartCoroutine(DoRotateToHit());
	}

	private IEnumerator DoRotateToHit()
	{
		additionalRotation = Quaternion.identity;
		float time2 = 0f;
		Quaternion centerRotation = Quaternion.AngleAxis(rotateAngle * (float)(-base.tableSide), racketRotateAxis);
		while (time2 < rotateDuration)
		{
			yield return null;
			time2 += Time.deltaTime;
			additionalRotation = Quaternion.Lerp(Quaternion.identity, centerRotation, time2 / rotateDuration);
		}
		time2 = 0f;
		while (time2 < rotateMoveBackDuration)
		{
			yield return null;
			time2 += Time.deltaTime;
			additionalRotation = Quaternion.Lerp(centerRotation, Quaternion.identity, time2 / rotateMoveBackDuration);
		}
	}

	private AIHitBallParams GetHitBallParams(Vector3 ballPos)
	{
		float num = ai.shotSpeed.Random(scoreDifference);
		RacketSettings.Racket currentRacket = base.match.ui.racketButton.currentRacket;
		float num2 = currentRacket.zLandingPosRange.Lerp(num);
		float num3 = currentRacket.heightOverNet.Lerp(num);
		float spiny = currentRacket.downSpin.Lerp(num);
		float timeToLand = currentRacket.ballVelocityRange.Lerp(num);
		float x = Table.halfwidth * ai.aimRange.Random() * (float)MathEx.SignZeroPositive(ballPos.x) * Mathf.Lerp(1f, -1f, ai.aim.Random(scoreDifference));
		Vector3 vector = new Vector3(x, Table.tabletopy, (float)(-base.tableSide) * Table.halfLength * num2);
		bool useNormalTime = true;
		float spinx = 0f;
		float airResistance = 0f;
		float rebound = 1f;
		float num4 = num * currentRacket.fingerSpeedRange.max;
		if (currentRacket.type == RacketSettings.SettingsType.RangeSettings)
		{
			RacketSettings.RacketRangeSettings racketRangeSettings = currentRacket.ranges[currentRacket.ranges.Count - 1];
			num4 = num * racketRangeSettings.fingerSpeedRange.max;
			RacketSettings.RacketRangeSettings settingsForRange = currentRacket.GetSettingsForRange(num4);
			num2 = settingsForRange.zLandingPosRange.Lerp(num);
			num3 = settingsForRange.heightOverNet.Lerp(num);
			spiny = settingsForRange.downSpin.Lerp(num);
			vector = new Vector3(x, Table.tabletopy, (float)(-base.tableSide) * Table.halfLength * num2);
			timeToLand = settingsForRange.GetTimeToLand(num, vector, ballPos);
			useNormalTime = settingsForRange.useNormalTime;
			Vector3 normalized = (vector - ballPos).OnGround().normalized;
			if (Mathf.Abs(normalized.x) > settingsForRange.normalizedSpinLimit)
			{
				spinx = Mathf.Sign(normalized.x) * settingsForRange.spin;
			}
			spinx = (float)((UnityEngine.Random.Range(0, 100) >= 50) ? 1 : (-1)) * Mathf.Clamp01(ai.spinx.Random(scoreDifference)) * settingsForRange.spin;
			airResistance = settingsForRange.airResistance;
			rebound = settingsForRange.rebound;
		}
		HitBallParams hitBallParams = default(HitBallParams);
		hitBallParams.landingPosition = vector;
		hitBallParams.airResistance = airResistance;
		hitBallParams.heightAboveNet = Table.netHeight + num3;
		hitBallParams.timeToLand = timeToLand;
		hitBallParams.spinx = spinx;
		hitBallParams.spiny = spiny;
		hitBallParams.useNormalTime = useNormalTime;
		hitBallParams.rebound = rebound;
		hitBallParams.racketTimeScale = currentRacket.timeScale;
		AIHitBallParams result = default(AIHitBallParams);
		result.hitBallParams = hitBallParams;
		result.usedSpeed = num4;
		return result;
	}

	private void HitBall(HitBallParams p)
	{
		if (!(ball == null))
		{
			if (isInServe)
			{
				Vector3 position = ball.position;
				position.y = Mathf.Max(racket.position.y, position.y);
				ball.position = position;
			}
			PhysicsBall physicsBall = ball;
			p.heightAboveNet *= ai.shotHeightMult;
			p.timeToLand *= ai.timeToLandMult;
			HitBall(p.landingPosition, p.heightAboveNet, p.timeToLand, p.spinx, p.spiny, isInServe, p.useNormalTime, p.airResistance, p.rebound, p.racketTimeScale);
			isInServe = false;
			float timeToStage = physicsBall.flightSequence.GetTimeToStage(physicsBall.flightSequence.LastStage());
			GGDebug.DebugLog(timeToStage);
			float gameTimescale = 1f + pingTime * 0.001f / Time.timeScale / timeToStage;
			GGGameTime.gameTimescale = gameTimescale;
			PrepareForReturn(physicsBall);
		}
	}

	public void HitBall(Vector3 landingPos, float heightAboveNet, float timeToLand, float spinx, float spiny, bool isServe, bool useNormalTime = true, float airResistance = 0f, float rebound = 1f, float racketTimeScale = 1f)
	{
		if (isServe || ball.flightSequence.isActive || ball.isBallInGame)
		{
			HitParameters hitParams = default(HitParameters);
			if (isServe)
			{
				AnalyticFlightTrajectory.SequenceSimulationParams sim = AnalyticFlightTrajectory.SequenceSimulationParams.DefaultRecordFlightParams(serveFlight, ball, landingPos, heightAboveNet, timeToLand, spinx, 0f);
				sim.airResistance = airResistance;
				sim.rebound = rebound;
				AnalyticFlightTrajectory instance = Singleton<AnalyticFlightTrajectory>.instance;
				instance.HitServe(ball.flightSequence, sim);
				ball.flightSequence.Reset();
				isInServe = false;
			}
			else
			{
				AnalyticFlightTrajectory.SequenceSimulationParams sim2 = AnalyticFlightTrajectory.SequenceSimulationParams.DefaultRecordFlightParams(serveFlight, ball, landingPos, heightAboveNet, timeToLand, spinx, spiny);
				sim2.useNormalTime = useNormalTime;
				sim2.airResistance = airResistance;
				sim2.rebound = rebound;
				AnalyticFlightTrajectory instance2 = Singleton<AnalyticFlightTrajectory>.instance;
				instance2.HitBall(ball.flightSequence, sim2);
				ball.flightSequence.Reset();
			}
			HitBallParams hitBallParams = default(HitBallParams);
			hitBallParams.ballPosition = ball.position;
			hitBallParams.landingPosition = landingPos;
			hitBallParams.heightAboveNet = heightAboveNet;
			hitBallParams.timeToLand = timeToLand;
			hitBallParams.spinx = spinx;
			hitBallParams.spiny = spiny;
			hitBallParams.isServe = isServe;
			hitBallParams.useNormalTime = useNormalTime;
			hitBallParams.airResistance = airResistance;
			hitBallParams.rebound = rebound;
			hitBallParams.racketTimeScale = racketTimeScale;
			ball.ResetTimescale();
			ball.racketTimeScale = racketTimeScale;
			ball.hitParams = hitParams;
			ball.isServeBall = isServe;
			ball.playerThatHitBall = this;
			ball = null;
			base.match.OnPlayerHitBall(this, hitBallParams);
			isInServe = false;
			base.match.ui.ballFollower.fire.SetActive(false);
		}
	}

	private void PrepareForReturn(PhysicsBall ball)
	{
		Vector3 landingPos = ball.flightSequence.landingPos;
		if (Table.IsPosOnTable(landingPos))
		{
			float moveSpeed = GetNewMoveSpeed() * 0.5f;
			Vector3 position = racket.position;
			float t = ai.returnPosition.Random(scoreDifference);
			position.z = Mathf.Lerp(racket.position.z, (float)base.tableSide * Table.halfLength, t);
			position.x = Mathf.Lerp(racket.position.x, 0f, t);
			MoveToWithSpeed(position, moveSpeed, null);
		}
	}

	private void UpdateMove()
	{
		if (!moving)
		{
			return;
		}
		moveTime += Time.deltaTime;
		float t = 1f;
		if (moveDuration > 0f)
		{
			t = MathEx.Hermite(moveTime / moveDuration);
		}
		racketPosition = Vector3.Lerp(startMovePos, endMovePos, t);
		if (moveTime >= moveDuration)
		{
			moving = false;
			if (onMoveComplete != null)
			{
				onMoveComplete();
			}
		}
	}

	private IEnumerator DoAdditionalMove(Vector3 start, Vector3 end, float delay, float durationBack, float durationFwd, Action onComplete)
	{
		if (delay > 0f)
		{
			yield return new WaitForSeconds(delay);
		}
		additionalMove = Vector3.zero;
		float time2 = 0f;
		while (time2 < durationBack)
		{
			time2 += Time.deltaTime;
			additionalMove = Vector3.Lerp(start, end, time2 / durationBack);
			yield return null;
		}
		time2 = 0f;
		while (time2 < durationFwd)
		{
			time2 += Time.deltaTime;
			additionalMove = Vector3.Lerp(end, start, time2 / durationFwd);
			yield return null;
		}
		if (onComplete != null)
		{
			onComplete();
		}
	}

	private void MoveToWithSpeed(Vector3 pos, float moveSpeed, Action onMoveComplete)
	{
		float duration = Vector3.Distance(pos, racket.position) / moveSpeed;
		MoveTo(pos, duration, onMoveComplete);
	}

	private void MoveTo(Vector3 pos, float duration, Action onMoveComplete)
	{
		moveTime = 0f;
		moveDuration = duration;
		moving = true;
		startMovePos = racket.position;
		endMovePos = pos;
		this.onMoveComplete = onMoveComplete;
	}

	public override void StartServe(PhysicsBall ball)
	{
		this.ball = ball;
		this.ball.SetKinematic(true);
		isInServe = true;
		Vector3 position = racket.position;
		position.y = Table.tabletopy;
		position.x = Mathf.Clamp(position.x, 0f - Table.halfwidth, Table.halfwidth);
		ball.position = position;
		FlightParams flightParams = default(FlightParams);
		flightParams.position = position;
		flightParams.velocity = ParabolicFlight.initialVelocityForSimpleCase(position, position, Table.tabletopy + 0.3f);
		flightParams.angularVelocity = Vector3.zero;
		FlightParams initialConditions = flightParams;
		serveFlight.gravity = Physics.gravity;
		serveFlight.SetInitialConditions(initialConditions, null);
		serveFlight.Reset();
		Vector3 positionToMoveTo = new Vector3(0f - Table.halfwidth, racketY, (float)base.tableSide * Table.halfLength * 1.25f);
		MoveToWithSpeed(positionToMoveTo, GetNewMoveSpeed(), delegate
		{
			AIHitBallParams hitParams = GetHitBallParams(positionToMoveTo);
			StartCoroutine(DoAdditionalMove(Vector3.zero, Vector3.forward * base.tableSide * 0.3f, 0f, 0.3f, 0.1f, delegate
			{
				HitBall(hitParams.hitBallParams);
			}));
		});
	}

	public override void AwaitServe()
	{
		ball = null;
		Vector3 pos = new Vector3(0f, racketY, (float)base.tableSide * Table.halfLength * 1.25f);
		float moveSpeed = 4f;
		MoveToWithSpeed(pos, moveSpeed, null);
	}

	public override void ReceiveBall(PhysicsBall ball)
	{
		this.ball = ball;
		this.ball.SetKinematic(true);
		StartCoroutine(DoReceiveBall(ball));
	}

	private float GetNewMoveSpeed()
	{
		float num = ai.moveSpeed.Random(scoreDifference);
		if (base.match.ball.isBallInGame)
		{
			float t = ai.tiredNumShotsRange.InverseLerp(base.match.gameType.numShotsInPoint);
			num *= Mathf.Lerp(1f, ai.tiredSpeedMult, t);
		}
		return num;
	}

	private IEnumerator DoReceiveBall(PhysicsBall ball)
	{
		yield return null;
		if (!Table.IsPosOnTable(ball.flightSequence.landingPos))
		{
			yield break;
		}
		FlightSequence.FlightPlanStage lastStage = ball.flightSequence.LastStage();
		float totalDuration = ball.flightSequence.flightDuration;
		Vector3 apexPos = lastStage.flight.PositionAt(lastStage.flight.apexTime);
		Vector3 positionToMoveTo = new Vector3(apexPos.x, racketY, apexPos.z);
		float durationTillHit = totalDuration - lastStage.duration + lastStage.flight.apexTime - Time.deltaTime;
		float moveSpeed = GetNewMoveSpeed();
		float moveDuration = Vector3Ex.HorizontalDistance(positionToMoveTo, racket.position) / moveSpeed;
		if (moveDuration <= durationTillHit)
		{
			AIHitBallParams hitParams = GetHitBallParams(positionToMoveTo);
			MoveTo(positionToMoveTo, durationTillHit, delegate
			{
				HitBall(hitParams.hitBallParams);
			});
			Vector3 normalized = (positionToMoveTo - hitParams.hitBallParams.landingPosition).OnGround().normalized;
			float num = 0.2f;
			float num2 = num * hitParams.usedSpeed;
			normalized *= num2;
			StartCoroutine(DoAdditionalMove(Vector3.zero, normalized, 0f, durationTillHit - num, num, null));
		}
	}
}
